﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Net;
using System.IO;
using System.Net.Cache;
using System.Text.RegularExpressions;

namespace X4NET.Net
{
    public class FtpLib
    {
        #region Private fields

        private string _hostName;
        private string _userName;
        private string _password;
        private FtpWebRequest _ftpRequest;
        private FtpWebResponse _ftpResponse;

        #endregion

        #region Public property

        public string HostName
        {
            get { return _hostName; }
            set { _hostName = value; }
        }

        public string UserName
        {
            get { return _userName; }
            set { _userName = value; }
        }

        public string Password
        {
            get { return _password; }
            set { _password = value; }
        }

        #endregion

        #region ctors

        private FtpLib(string hostName, string userName, string password)
        {
            _hostName = hostName;
            _userName = userName;
            _password = password;
        }

        #endregion

        #region Factory methods

        public static FtpLib Setup(string hostName, string userName, string password)
        {
            return new FtpLib(hostName, userName, password);
        }

        #endregion

        #region Public methods

        public bool MakeDirectory(string remotePath)
        {
            try
            {
                if (!remotePath.StartsWith("/")) remotePath = string.Format(@"/{0}", remotePath);
                string hostPath = string.Format("ftp://{0}{1}", _hostName, remotePath);
                _ftpRequest = (FtpWebRequest)WebRequest.Create(hostPath);
                _ftpRequest.Credentials = new NetworkCredential(_userName, _password);
                _ftpRequest.Method = WebRequestMethods.Ftp.MakeDirectory;
                _ftpResponse = (FtpWebResponse)_ftpRequest.GetResponse();
                _ftpResponse.Close();

                return true;
            }
            catch (Exception exception)
            {
                Trace.WriteLine(string.Format("Exception: {0}", exception), "FtpLib.MakeDirectory");
                return false;
            }
        }

        public bool UploadFile(string remotePath, byte[] file)
        {
            try
            {
                if (!remotePath.StartsWith("/")) remotePath = string.Format(@"/{0}", remotePath);
                string hostPath = string.Format("ftp://{0}{1}", _hostName, remotePath);

                _ftpRequest = (FtpWebRequest)WebRequest.Create(hostPath);
                _ftpRequest.Credentials = new NetworkCredential(_userName, _password);
                _ftpRequest.Method = WebRequestMethods.Ftp.UploadFile;
                _ftpRequest.ContentLength = file.Length;

                Stream requestStream = _ftpRequest.GetRequestStream();
                requestStream.Write(file, 0, file.Length);
                requestStream.Close();

                var response = (FtpWebResponse)_ftpRequest.GetResponse();
                response.Close();

                return true;
            }
            catch (Exception exception)
            {
                Trace.WriteLine(string.Format("Exception: {0}", exception), "FtpLib.UploadFile");
                return false;
            }
        }

        public byte[] DownloadFile(string fileName)
        {
            const int NUM_BYTES_READ = 0;
            const int NUM_BYTES_TO_READ = 1024;

            Stream responseStream = null;

            try
            {
                if (!fileName.StartsWith("/")) fileName = string.Format(@"/{0}", fileName);
                string hostPathAndFileName = string.Format("ftp://{0}{1}", _hostName, fileName);

                _ftpRequest = (FtpWebRequest)WebRequest.Create(hostPathAndFileName);
                _ftpRequest.Credentials = new NetworkCredential(_userName, _password);
                _ftpRequest.Method = WebRequestMethods.Ftp.DownloadFile;

                _ftpResponse = (FtpWebResponse)_ftpRequest.GetResponse();
                responseStream = _ftpResponse.GetResponseStream();

                var stream = new MemoryStream();
                var buffer = new byte[NUM_BYTES_TO_READ];
                if (responseStream != null)
                {
                    int bytesRead = responseStream.Read(buffer, NUM_BYTES_READ, NUM_BYTES_TO_READ);
                    while (bytesRead != 0)
                    {
                        stream.Write(buffer, 0, bytesRead);
                        bytesRead = responseStream.Read(buffer, 0, 1024);
                    }
                }

                return stream.ToArray();
            }
            catch (Exception exception)
            {
                Trace.WriteLine(string.Format("Exception: {0}", exception), "FtpLib.DownloadFile");
                throw;
            }
            finally
            {
                if (responseStream != null)
                {
                    responseStream.Close();
                }
            }
        }

        public List<string> GetFilesInDirectory(string directoryPath)
        {
            var outputList = new List<string>();

            List<string> detailedList;
            List<string> simpleList;

            InitLists(directoryPath, out detailedList, out simpleList);

            foreach (string detailedListRow in detailedList)
            {
                if (detailedListRow.StartsWith("-"))
                {
                    foreach (string simpleListRow in simpleList)
                    {
                        string name = GetName(simpleListRow, simpleListRow);
                        if (detailedListRow.IndexOf(name, StringComparison.Ordinal) >= 0)
                        {
                            outputList.Add(name);
                            break;
                        }
                    }
                }
            }

            return outputList;
        }

        public List<string> GetDirectoryList(string path)
        {
            var outputList = new List<string>();

            List<string> detailedList;
            List<string> simpleList;

            InitLists(path, out detailedList, out simpleList);

            foreach (string detailedListRow in detailedList)
            {
                if (detailedListRow.StartsWith("d") && !detailedListRow.EndsWith("."))
                {
                    foreach (string simpleListRow in simpleList)
                    {
                        string name = GetName(simpleListRow, simpleListRow);
                        if (detailedListRow.IndexOf(name, StringComparison.Ordinal) >= 0)
                        {
                            outputList.Add(name);
                            break;
                        }
                    }
                }
            }

            return outputList;
        }

        public List<string> GetDirectoryListByFileName(string startPath, string fileName)
        {
            var result = new List<string>();

            FillDirectoryList(startPath, fileName, ref result);

            return result;
        }

        public List<string> GetFileListByMask(string startPath, string mask)
        {
            var result = new List<string>();

            FillFileListByMask(startPath, mask, ref result);

            return result;
        }

        #endregion

        #region Private methods

        private void FillDirectoryList(string startPath, string fileName, ref List<string> outputList)
        {
            if (outputList == null) outputList = new List<string>();
            List<string> files = GetFilesInDirectory(startPath);
            if (files.Contains(fileName)) outputList.Add(startPath);
            List<string> dirs = GetDirectoryList(startPath);
            if (dirs.Count > 0)
            {
                foreach (string dir in dirs)
                {
                    FillDirectoryList(Path.Combine(startPath, dir), fileName, ref outputList);
                }
            }
        }

        private void FillFileListByMask(string startPath, string mask, ref List<string> outputList)
        {
            if (outputList == null) outputList = new List<string>();
            List<string> files = GetFilesInDirectory(startPath);
            outputList = GetFileListByMask(outputList, files, mask);
            List<string> directories = GetDirectoryList(startPath);
            if (directories.Count > 0)
            {
                foreach (string dir in directories)
                {
                    FillFileListByMask(Path.Combine(startPath, dir), mask, ref outputList);
                }
            }
        }

        private static List<string> GetFileListByMask(List<string> outputList, IEnumerable<string> files, string mask)
        {
            foreach (string file in files)
            {
                if (Regex.IsMatch(file.ToUpper(), mask.ToUpper()))
                {
                    if (!outputList.Contains(file)) outputList.Add(file);
                }
            }

            return outputList;
        }

        private static string GetName(string s, string t)
        {
            int i = s.LastIndexOf("/", StringComparison.Ordinal);
            if (i >= 0) t = s.Substring(i + 1, s.Length - (i + 1));

            return t;
        }

        private void InitLists(string path, out List<string> detailedList, out List<string> simpleList)
        {
            detailedList = GetList(path, WebRequestMethods.Ftp.ListDirectoryDetails);
            simpleList = GetList(path, WebRequestMethods.Ftp.ListDirectory);
        }

        private List<string> GetList(string path, string method)
        {
            var list = new List<string>();

            try
            {
                if (string.IsNullOrEmpty(path)) path = "/";
                else if (!path.StartsWith("/")) path = string.Format("/{0}", path);

                _ftpRequest = (FtpWebRequest)WebRequest.Create(string.Format("ftp://{0}{1}", HostName, path));
                _ftpRequest.Credentials = new NetworkCredential(_userName, _password);
                _ftpRequest.Method = method;
                _ftpRequest.CachePolicy = new RequestCachePolicy(RequestCacheLevel.NoCacheNoStore);

                _ftpResponse = (FtpWebResponse)_ftpRequest.GetResponse();
                Stream stream = _ftpResponse.GetResponseStream();

                try
                {
                    if (stream != null)
                    {
                        using (var reader = new StreamReader(stream))
                        {
                            while (!reader.EndOfStream)
                            {
                                list.Add(reader.ReadLine());
                            }
                        }
                    }
                }
                finally
                {
                    _ftpResponse.Close();

                    if (stream != null)
                    {
                        stream.Close();
                    }
                }

                return list;
            }
            catch (Exception exception)
            {
                Trace.WriteLine(exception.ToString(), "FtpLib.GetList");
                throw;
            }
        }

        #endregion
    }
}
